package org.study.lucene.api.index;

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queries.spans.*;
import org.apache.lucene.queryparser.classic.*;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.study.lucene.api.constant.PathConstants;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * 参考 https://www.cnblogs.com/bestlmc/p/11865725.html
 *
 * @author Administrator
 * @date 2021-12-27
 */
public class IndexFromFileTest {
    public static void main(String[] arg) throws IOException, ParseException, InvalidTokenOffsetsException {
        // 创建索引测试
        testCreateIndex();
        // 索引查询
        testMatchAllDocsQuery();
        testMatchNoDocsQuery();
        testTermQuery();
        testBooleanQuery();
        testPhraseQuery();
        testMultiPhraseQuery();
        testSpanNearQuery();
        testTermRangeQuery();
        testPointRangeQuery();
        testPrefixQuery();
        testWildcardQuery();
        testRegexpQuery();
        testFuzzyQuery();
        // 创建查询条件，使用 MatchAllDocsQuery 查询索引目录中的所有文档
        Query query = new MatchAllDocsQuery();
        testSortSearch(query);
        testSortAndHighlighterSearch(query);
        testQueryParser();
        testMultiFieldQueryParser();
    }

    public static void testCreateIndex() throws IOException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 指定一个标准分析器，对文档内容进行分析
        Analyzer analyzer = new StandardAnalyzer();
        // 创建 IndexWriterConfig 对象
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
        // 创建 IndexWriter 对象
        IndexWriter indexWriter = new IndexWriter(directory, config);
        // 原始文档的路径
        File dataFile = new File(PathConstants.DATA_PATH);
        File[] fileList = dataFile.listFiles();
        for (File file : fileList) {
            // 创建 Document 对象
            Document document = new Document();
            // 创建 Field(域)对象(参数含义：域的名称、域的内容、是否存储)并添加到 Document 对象中
            // 文件名称域
            document.add(new StringField("fileName", file.getName(), Field.Store.YES));
            // 文件大小域
            // document.add(new LongField("fileSize", FileUtils.sizeOf(file), Field.Store.YES));
            document.add(new NumericDocValuesField("fileSize", FileUtils.sizeOf(file)));
            // 文件路径域：不分析、不索引、只存储
            document.add(new StoredField("filePath", file.getPath()));
            // 文件内容域
            String fileContent = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
            document.add(new TextField("fileContent", fileContent, Field.Store.YES));
            // 使用 IndexWriter 对象将 Document 对象写入索引库，此过程进行索引创建，并将索引和 document 对象写入索引库
            indexWriter.addDocument(document);
        }
        // 关闭 IndexWriter 对象
        indexWriter.close();
    }

    /**
     * MatchAllDocsQuery：查询所有文档
     */
    public static void testMatchAllDocsQuery() throws IOException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 创建查询条件，使用 MatchAllDocsQuery 查询索引目录中的所有文档
        Query query = new MatchAllDocsQuery();
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 MatchAllDocsQuery 查询索引目录中的所有文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * MatchNoDocsQuery：不匹配任何文档
     */
    public static void testMatchNoDocsQuery() throws IOException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 创建查询条件，使用 MatchNoDocsQuery 不匹配任何文档
        Query query = new MatchNoDocsQuery();
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 MatchNoDocsQuery 不匹配任何文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * TermQuery：精准查询，需指定要查询的域和要查询的关键词
     * TermQuery 通过项查询，由于 TermQuery 不使用分析器，因此建议匹配不分词的 Field 域查询，如：订单号、分类ID号等
     */
    public static void testTermQuery() throws IOException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 使用 TermQuery 创建一个精准查询查询条件，需指定查询的域和查询的关键词
        Query query = new TermQuery(new Term("fileName", "阿里巴巴 Java 开发手册(20161207公开版).pdf"));
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 TermQuery 精准查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * BooleanQuery：布尔查询
     * <p>
     * 搜索条件往往是多个的，如：要查询名称包含 “电脑” 或 “thinkpad”的商品，就需要两个词项查询做或合并，布尔查询就是用来组合多个子查询的。
     * 每个子查询称为布尔字句 BooleanClause，布尔字句自身也可以是组合的，组合关系支持如下四种：
     * Occur.SHOULD(或)、Occur.MUST(且)、Occur.MUST_NOT(且非)、Occur.FILTER(同 MUST，但该字句不参与评分)
     * <p>
     * 布尔查询默认的最大字句数为 1024，在将通配符查询这样的查询 rewriter 为布尔查询时，往往会产生很多的字句，可能抛出 TooManyClauses 异常。
     * 可通过 BooleanQuery.setMaxClauseCount(int) 设置最大字句数。
     */
    public static void testBooleanQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder();
        // 查询条件1
        QueryParser queryParser = new QueryParser("fileName", new IKAnalyzer());
        Query query1 = queryParser.parse("正式版");
        booleanQueryBuilder.add(query1, BooleanClause.Occur.MUST);
        // 查询条件2
        Query query2 = new FuzzyQuery(new Term("fileSize", "西游记"));
        booleanQueryBuilder.add(query2, BooleanClause.Occur.MUST);
        // 查询条件3
        Query query3 = new TermQuery(new Term("filePath", "小说"));
        booleanQueryBuilder.add(query3, BooleanClause.Occur.MUST);
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(booleanQueryBuilder.build(), 10);
        // 查询结果总条数
        System.out.println("使用 BooleanQuery 布尔查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * PhraseQuery：短语查询，最常用的查询，匹配特定序列的多个词项。
     * PhraseQuery 使用一个位置移动因子(slop)来决定任意两个词项的位置可最大移动多少个位置来进行匹配，默认为 0。
     * 有两种方式来构建对象
     * 注意：所有加入的词项都匹配才算匹配(即使是在同一位置加入多个词项)，如果需要在同一位置匹配多个同义词中的一个，适合用 MultiPhraseQuery
     */
    public static void testPhraseQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        PhraseQuery query1 = new PhraseQuery("fileContent", "根据", "明代");
        PhraseQuery query2 = new PhraseQuery(0, "fileContent", "根据", "明代");
        PhraseQuery query3 = new PhraseQuery("fileContent", "笔记本电脑", "联想");

        PhraseQuery query4 = new PhraseQuery.Builder()
                .add(new Term("fileContent", "根据"), 4)
                .add(new Term("fileContent", "施耐"), 5).build();
        // 这两句等同
        PhraseQuery query5 = new PhraseQuery.Builder()
                .add(new Term("fileContent", "笔记本电脑"), 0)
                .add(new Term("fileContent", "联想"), 1).build();

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query1, 10);
        // 查询结果总条数
        System.out.println("使用 PhraseQuery 短语查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * MultiPhraseQuery：多重短语查询
     * 短语查询的一种更通用的用法，支持同位置多个词的 OR 匹配，通过里面的 Builder 来构建 MultiPhraseQuery
     */
    public static void testMultiPhraseQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        Term[] terms = new Term[2];
        terms[0] = new Term("fileContent", "根据");
        terms[1] = new Term("fileContent", "根据明代");
        Term term = new Term("fileContent", "施耐");
        MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder()
                .add(terms)
                .add(term)
                .build();
        // 对比 PhraseQuery 在同位置加入多个词，同位置的多个词都需匹配，所以查不出
        PhraseQuery query = new PhraseQuery.Builder()
                .add(terms[0], 0)
                .add(terms[1], 0)
                .add(term, 1)
                .build();

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(multiPhraseQuery, 10);
        // 查询结果总条数
        System.out.println("使用 MultiPhraseQuery 多重短语查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * SpanNearQuery：临近查询(跨度查询)
     * 用于更复杂的短语查询，可以指定词间位置的最大间隔跨度。
     * 通过组合一系列的 SpanQuery 实例来进行查询，可以指定是否按顺序匹配、slop、gap
     */
    public static void testSpanNearQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // SpanNearQuery 临近查询
        SpanTermQuery spanTermQuery1 = new SpanTermQuery(new Term("fileName", "中央电视台"));
        SpanTermQuery spanTermQuery2 = new SpanTermQuery(new Term("fileName", "无锡"));
        SpanNearQuery query1 = new SpanNearQuery(new SpanQuery[]{spanTermQuery1, spanTermQuery2}, 0, true);
        // SpanNearQuery 临近查询：gap、slop 的使用
        SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery.newOrderedNearQuery("fileName")
                .addClause(spanTermQuery1)
                .addGap(0)
                .setSlop(1)
                .addClause(spanTermQuery2);
        SpanNearQuery query2 = spanNearQueryBuilder.build();

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query1, 10);
        // 查询结果总条数
        System.out.println("使用 SpanNearQuery 临近查询(跨度查询)的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * TermRangeQuery：词项范围查询，用于查询包含某个范围内的词项的文档，如以字母开头a到c的词项。
     * 词项在反向索引中是排序的，只需指定的开始词项、结束词项，就可以查询该范围的词项(注：如果是做数值的范围查询则用 PointRangeQuery)。
     * TermRangeQuery 参数说明：
     * 参数 field：要查询的字段
     * 参数 lowerTerm：下边界词
     * 参数 upperTerm：上边界词
     * 参数 includeLower：是否包含下边界
     * 参数 includeUpper：是否包含上边界
     */
    public static void testTermRangeQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // TermRangeQuery 词项范围查询
        TermRangeQuery query = TermRangeQuery.newStringRange("fileName", "中央电视台", "同名小说改编", false, true);

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 TermRangeQuery 词项范围查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * PointRangeQuery：根据数值范围查询
     */
    public static void testPointRangeQuery() throws IOException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 使用 PointRangeQuery 创建一个根据数值范围查询条件，参数说明：查询的域、最小值、最大值
        Query query = LongPoint.newRangeQuery("fileSize", 41L, 2055L);
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 PointRangeQuery 根据数值范围查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * PrefixQuery：前缀查询，查询包含以 xxx 为前缀的词项的文档，是通配符查询，如 app 实际是 app*
     */
    public static void testPrefixQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // PrefixQuery 前缀查询
        PrefixQuery query = new PrefixQuery(new Term("fileName", "中国"));

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 PrefixQuery 前缀查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * WildcardQuery：通配符查询，* 表示0个或多个字符、? 表示1个字符，\是转义符。
     * 通配符查询可能会比较慢，不可以通配符开头(那样就是所有词项了)
     */
    public static void testWildcardQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // WildcardQuery 通配符查询
        WildcardQuery query = new WildcardQuery(new Term("fileName", "中国*"));

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 WildcardQuery 通配符查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * RegexpQuery：正则表达式查询，词项符合某正则表达式
     */
    public static void testRegexpQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // RegexpQuery 正则表达式查询
        RegexpQuery query = new RegexpQuery(new Term("fileName", "厉害.{4}"));

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 RegexpQuery 正则表达式查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * FuzzyQuery：模糊查询，简单地与索引词项进行相近匹配，允许最大 2 个不同字符。
     * 常用于拼写错误的容错：如把 thinkpad 拼成 thinkppd 或 thinkd，使用 FuzzyQuery 仍可搜索到正确的结果。
     */
    public static void testFuzzyQuery() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // FuzzyQuery 模糊查询
        FuzzyQuery query1 = new FuzzyQuery(new Term("fileName", "猪八戒"));
        FuzzyQuery query2 = new FuzzyQuery(new Term("fileName", "thinkd"), 2);
        FuzzyQuery query3 = new FuzzyQuery(new Term("fileName", "thinkpaddd"));
        FuzzyQuery query4 = new FuzzyQuery(new Term("fileName", "thinkdaddd"));

        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        TopDocs topDocs = indexSearcher.search(query1, 10);
        // 查询结果总条数
        System.out.println("使用 FuzzyQuery 模糊查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * 排序查询
     */
    public static void testSortSearch(Query query) throws IOException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // SortField.Type.SCORE：根据相关度进行排序(默认)
        // SortField.Type.DOC：根据文档编号或者说是索引顺序
        // SortField.Type.FLOAT(Long等)：根据 field 的数值类型进行排序
        // true 表示降序
        SortField sortField = new SortField("fileSize", SortField.Type.INT, false);
        Sort sort = new Sort(sortField);
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数，第三个参数是排序对象
        TopDocs topDocs = indexSearcher.search(query, 10, sort);

        // 查询结果总条数
        System.out.println("使用 Sort 排序查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    /**
     * 排序查询，高亮显示
     *
     * @param query
     */
    public static void testSortAndHighlighterSearch(Query query) throws IOException, InvalidTokenOffsetsException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 创建 IndexReader 对象，需要指定 Directory 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 创建 IndexSearcher 对象，需要指定 IndexReader 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        // SortField.Type.SCORE：根据相关度进行排序(默认)
        // SortField.Type.DOC：根据文档编号或者说是索引顺序
        // SortField.Type.FLOAT(Long等)：根据 field 的数值类型进行排序
        // true 表示降序
        SortField sortField = new SortField("fileSize", SortField.Type.INT, false);
        Sort sort = new Sort(sortField);
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数，第三个参数是排序对象
        TopDocs topDocs = indexSearcher.search(query, 10, sort);

        // 查询结果总条数
        System.out.println("使用 Sort 排序查询、Highlighter 高亮显示的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document document = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document document1 = indexSearcher.doc(docId);
            String text = document.get("fileName");
            SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");
            Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
            highlighter.setTextFragmenter(new SimpleFragmenter(text.length()));
            if (text != null) {
                Analyzer ikAnalyzer = new IKAnalyzer();
                TokenStream tokenStream = ikAnalyzer.tokenStream("fileName", new StringReader(text));
                String highLightText = highlighter.getBestFragment(tokenStream, text);
                System.out.println("高亮显示第 " + (docId + 1) + " 条检索结果如下所示：");
                System.out.println("fileName: " + highLightText);
            }
            String fileName = document.get("fileName");
            String fileSize = document.get("fileSize");
            String filePath = document.get("filePath");
            String fileContent = document.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        // 关闭 IndexReader 对象
        indexReader.close();
    }

    public static void testQueryParser() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 索引读取工具
        IndexReader indexReader = DirectoryReader.open(directory);
        // 索引搜索工具
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 创建查询解析器，参数含义：默认要查询的字段的名称、分词器
        QueryParser queryParser = new QueryParser("fileName", new IKAnalyzer());
        // 如果想同时匹配多个：QueryParser parser = new MultiFieldQueryParser(new String[]{"field1", "field2"}, new IKAnalyzer());
        // 创建查询对象
        Query query = queryParser.parse("正式版");
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        // 返回结果：按照匹配度排名得分前 N 名的文档信息，包含查询到的总条数、符合条件的文档编号等信息
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 QueryParser 查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document doc = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document doc = indexSearcher.doc(docId);
            String fileName = doc.get("fileName");
            String fileSize = doc.get("fileSize");
            String filePath = doc.get("filePath");
            String fileContent = doc.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        indexReader.close();
    }

    public static void testMultiFieldQueryParser() throws IOException, ParseException {
        // 指定索引库的存放位置 Directory 对象。索引库还可以存放到内存中，如：Directory directory = new RAMDirectory();
        Directory directory = FSDirectory.open(new File(PathConstants.INDEX_PATH).toPath());
        // 索引读取工具
        IndexReader indexReader = DirectoryReader.open(directory);
        // 索引搜索工具
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 创建查询解析器，参数含义：默认要查询的字段的名称、分词器
        String[] fields = {"fileName", "fileSize", "filePath"};
        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new IKAnalyzer());
        // 创建查询对象
        Query query = queryParser.parse("正式版");
        // 执行查询，参数说明：第一个参数是查询对象，第二个参数是要查询的最大结果条数
        // 返回结果：按照匹配度排名得分前 N 名的文档信息，包含查询到的总条数、符合条件的文档编号等信息
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 查询结果总条数
        System.out.println("使用 MultiFieldQueryParser 查询的文档数：" + topDocs.totalHits);
        // 遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        StoredFields storedFields = indexReader.storedFields();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docId = scoreDoc.doc;
            // 根据 docId 找到 Document 对象
            Document doc = storedFields.document(docId);
            // 查看源码发现：IndexSearcher.doc(int docId) 底层也是调用的 IndexReader.document(int docId)
            // Document doc = indexSearcher.doc(docId);
            String fileName = doc.get("fileName");
            String fileSize = doc.get("fileSize");
            String filePath = doc.get("filePath");
            String fileContent = doc.get("fileContent");
            String format = String.format("fileName：%s，fileSize：%s，filePath：%s", fileName, fileSize, filePath);
            System.out.println("docId=" + docId + "，得分： " + scoreDoc.score + "\n" + format);
        }
        indexReader.close();
    }
}
